package nimbus.commands
{
	import nimbus.commands.base.ICommand;
	import nimbus.commands.base.IUndoableCommand;
	import nimbus.events.ExecutionEvent;
	
	/**
	 * The command base to use when a group of subcommands should be processed in parallel.
	 * The subcommands passed through the constructor will be processed in parallel and the command
	 * will complete execution once all the subcommands have completed execution.
	 * 
	 * <p>This command may be used as a subcommand within another ParallelCommand or 
	 * SequenceCommand.</p>
	 * 
	 * @see nimbus.commands.SequenceCommand
	 */
	public class ParallelCommand extends AsyncCommand
	{
		/**
		 * The subcommands to be executed in parallel.
		 */
		protected var subcommands:Array;
		
		/**
		 * The subcommands still in the process of executing.
		 */
		protected var subcommandsQueue:Array;
		
		/**
		 * Whether all subcommands executed successfully.
		 */
		protected var subcommandsSuccessful:Boolean = true;
		
		/**
		 * Whether to use subcommand processing messages to provide user feedback.
		 * 
		 * @see nimbus.Controller#deriveProcessingMessage()
		 * 
		 * @default false
		 */
		public var useSubcommandProcessingMessages:Boolean = false;
		
		/**
		 * Constructor.
		 * 
		 * @param subcommands The group of subcommands to be executed in parallel.
		 */
		public function ParallelCommand(subcommands:Array=null)
		{
			this.subcommands = subcommands;
		}
		
		/**
		 * Executes all subcommands in parallel.
		 */
		override public function execute():void
		{
			if (subcommands && subcommands.length > 0)
			{
				// Use a copy of subcommands for the queue.  Remember that each subcommand
				// may be of type AsyncCommand but may not necessarily be synchronous in the true
				// sense of the word.  A ParallelCommand or SequenceCommand are of type AsyncCommand
				// but if all their subcommands are synchronous, their code executes synchronously.
				// The same is true for any AsyncCommand that doesn't actually send out
				// an asynchronous request to a server.
				// For this reason, we can't just start out with an empty subcommands queue and
				// add each asynchronous command as we get to it in our for each loop. If the first
				// subcommand were a SequenceCommand full of synchronous subcommands, we added it
				// to the queue, and then called execute() on it, it would immediately dispatch
				// execution complete which would then, in subcommandComplete(), remove the command 
				// from the array and the queue would be empty.  Therefore this parallel command 
				// would dispatch execution complete, even though we're still processing our for 
				// each loop. By setting up the queue will all our subcommands in the first place 
				// and remove each SyncCommand, we avoid this possible situation.
				subcommandsQueue = subcommands.slice();
				
				// Use subcommands array for looping instead of subcommandsQueue array.
				// If the subcommand is a SyncCommand, we immediately remove it from the queue
				// array.  If we were to use the queue array for our looping, the next item in the
				// queue array would be skipped due to this removal.
				for each (var subcommand:ICommand in subcommands)
				{
					if (subcommand is SequenceCommand)
					{
						SequenceCommand(subcommand).useSubcommandProcessingMessages = 
								useSubcommandProcessingMessages;
					} else if (subcommand is ParallelCommand)
					{
						ParallelCommand(subcommand).useSubcommandProcessingMessages =
								useSubcommandProcessingMessages;
					}
					
					if (subcommand is AsyncCommand)
					{
						AsyncCommand(subcommand).addEventListener(ExecutionEvent.EXECUTION_COMPLETE,
								asyncSubcommandComplete);
						AsyncCommand(subcommand).execute();
					}
					else
					{
						try
						{
							ICommand(subcommand).execute();
						}
						catch (error:Error)
						{
							subcommandComplete(subcommand, false);
							throw error;
						}
						subcommandComplete(subcommand, true);
					}
				}
				
				if (useSubcommandProcessingMessages)
				{
					deriveProcessingMessage();
				}
			}
			else
			{
				parallelComplete(true);
			}
		}
		
		/**
		 * Handles asynchronous command completion.  Cleans up listeners.
		 */
		protected function asyncSubcommandComplete(event:ExecutionEvent):void
		{
			AsyncCommand(event.target).removeEventListener(ExecutionEvent.EXECUTION_COMPLETE,
					asyncSubcommandComplete);
			subcommandComplete(ICommand(event.target), event.success);
		}
		
		/**
		 * Called each time a subcommand completes execution.
		 */
		protected function subcommandComplete(subcommand:ICommand, success:Boolean):void
		{
			if (!success)
			{
				subcommandsSuccessful = false;
			}
			
			// We only care about updating the processing message if the subcommand was
			// asynchronous since synchronous commands don't really have processing messages.
			if (useSubcommandProcessingMessages && subcommand is AsyncCommand)
			{
				deriveProcessingMessage();
			}
			
			subcommandsQueue.splice(subcommandsQueue.indexOf(subcommand), 1);
			
			if (subcommandsQueue.length == 0)
			{
				parallelComplete(subcommandsSuccessful);
			}
		}
		
		/**
		 * Called once all subcommands have completed execution.  The extending class may override
		 * this function to execute any last-minute logic before the command completes execution.
		 */ 
		protected function parallelComplete(success:Boolean):void
		{
			if (success)
			{
				dispatchSuccess();
			}
			else
			{
				dispatchFailure();
			}
		}
		
		/**
		 * Analyzes currently executing subcommands to determine which processing message should
		 * be used to provide feedback to the user.  The first processing message found in the
		 * list of processing subcommands will be used.
		 * 
		 * @see nimbus.Controller#deriveProcessingMessage()
		 */
		protected function deriveProcessingMessage():void
		{
			for each (var command:ICommand in subcommandsQueue)
			{
				if (command is AsyncCommand && AsyncCommand(command).processingMessage != null)
				{
					processingMessage = AsyncCommand(command).processingMessage;
					return;
				}
			}
			
			processingMessage = null;
		}
		
		/**
		 * the undo method, it checks for any IUndoable commands and calls their undo
		 * 
		 */ 
		 public function undo():void
		 {
		 	for each (var subcommand:ICommand in subcommands)
			{
				if(subcommand is IUndoableCommand)
				{
					(subcommand as IUndoableCommand).undo();
				}
			} 
		 }
	}
}